// Fill out your copyright notice in the Description page of Project Settings.

#include "WorldEntityManagerDriverFile.h"
#include "WorldEntityManager.h"
#include <cctype>
#include "../geom/ODRoadAPI.h"

namespace
{
	// Temporarily change the name to "object" as WorldDB use this convension..
	class ObjectAdapter 
	{
		pugi::xml_node&	_node;
		std::string		_name;

	public:
		ObjectAdapter(pugi::xml_node& node)
			: _node(node)
			, _name(node.name())
		{
			_node.set_name("object");
		}

		~ObjectAdapter()
		{
			_node.set_name(_name.c_str());
		}
	};
}

struct FWorldEntityManagerDriverFile::Opaque
{
	std::wstring		FilePath;
	pugi::xml_document	FileSource;
};

FWorldEntityManagerDriverFile::FWorldEntityManagerDriverFile()
	: _op(std::make_shared<Opaque>())
{}

void FWorldEntityManagerDriverFile::Connect(const std::string& content)
{
	// pugi uses wide or native encoding for file paths.. use wide here.
	_op->FilePath = pugi::as_wide(content);

	// Parse the OpenDRIVE xml document.
	pugi::xml_parse_result result =_op->FileSource.load_string(content.c_str());

	if (!result)
	{
		throw Exception(result.description());
	}

    // load parse opendrive for mesh generation..
	// Temporarily disable the junction mesh welding
    // InitializeODRoadSystem(_op->FilePath.c_str(), 100.0);
}

int FWorldEntityManagerDriverFile::GetRevision(int world)
{
	return 1; // No revision...
}

FWorldEntityManagerPool::Ptr FWorldEntityManagerDriverFile::GetRegion(double, double, double, double, int, int)
{
	// JSON representation for WorldDB records. (template)
	const json jsonDB = {
		{"geom", ""},
		{"idWorld", -1},
		{"revisionStart", -1},
		{"revisionEnd", -1},
		{"idReferences", std::vector<std::int64_t>()}
	};

	FWorldEntityManagerPool::Ptr pool =
		std::make_shared<FWorldEntityManagerPool>();

	pugi::xml_node top = _op->FileSource.child("OpenDRIVE");

	for (auto& road : top.children("road"))
	{
		ObjectAdapter g(road);

		std::stringstream ss;
		road.print(ss);

		json j = {
			{"id", road.attribute("id").as_llong()},
			{"idParent", -1},
			{"dataType", "Road"},
			{"data", ss.str()}
		};
		j.merge_patch(jsonDB);
		pool->Add(std::make_shared<FWorldRoad>(j));

		pugi::xml_node objects = road.child("objects");
		if (objects)
		{
			for (auto& object : objects.children("object"))
			{
				ObjectAdapter gg(object);

				std::stringstream sss;
				object.print(sss);

				json jj = {
					{"id", object.attribute("id").as_llong()},
					{"idParent", road.attribute("id").as_llong()},
					{"dataType", "Object"},
					{"data", sss.str()}
				};
				jj.merge_patch(jsonDB);
				pool->Add(std::make_shared<FWorldRoadObject>(jj));
			}

			for (auto& tunnel : objects.children("tunnel"))
			{
				ObjectAdapter gg(tunnel);

				std::stringstream sss;
				tunnel.print(sss);

				json jj = {
					{"id", tunnel.attribute("id").as_llong()},
					{"idParent", road.attribute("id").as_llong()},
					{"dataType", "Tunnel"},
					{"data", sss.str()}
				};
				jj.merge_patch(jsonDB);
				pool->Add(std::make_shared<FWorldTunnel>(jj));
			}

			for (auto& bridge : objects.children("bridge"))
			{
				ObjectAdapter gg(bridge);

				std::stringstream sss;
				bridge.print(sss);

				json jj = {
					{"id", bridge.attribute("id").as_llong()},
					{"idParent", road.attribute("id").as_llong()},
					{"dataType", "Bridge"},
					{"data", sss.str()}
				};
				jj.merge_patch(jsonDB);
				pool->Add(std::make_shared<FWorldBridge>(jj));
			}
		}

		pugi::xml_node signals = road.child("signals");
		if (signals)
		{
			for (auto& signal : signals.children("signal"))
			{
				ObjectAdapter gg(signal);

				std::stringstream sss;
				signal.print(sss);

				json jj = {
					{"id", signal.attribute("id").as_llong()},
					{"idParent", road.attribute("id").as_llong()},
					{"dataType", "Signal"},
					{"data", sss.str()}
				};
				jj.merge_patch(jsonDB);
				pool->Add(std::make_shared<FWorldSignal>(jj));
			}
		}
	}

	for (auto& controller : top.children("controller"))
	{
		ObjectAdapter g(controller);

		std::stringstream ss;
		controller.print(ss);

		json j = {
			{"id", controller.attribute("id").as_llong()},
			{"idParent", -1},
			{"dataType", "Controller"},
			{"data", ss.str()}
		};
		j.merge_patch(jsonDB);
		pool->Add(std::make_shared<FWorldController>(j));
	}

	for (auto& junction : top.children("junction"))
	{
		ObjectAdapter g(junction);

		std::stringstream ss;
		junction.print(ss);

		json j = {
			{"id", junction.attribute("id").as_llong()},
			{"idParent", -1},
			{"dataType", "Junction"},
			{"data", ss.str()}
		};
		j.merge_patch(jsonDB);
		pool->Add(std::make_shared<FWorldJunction>(j));
	}

	for (auto& junctionGroup : top.children("junctionGroup"))
	{
		ObjectAdapter g(junctionGroup);

		std::stringstream ss;
		junctionGroup.print(ss);

		json j = {
			{"id", junctionGroup.attribute("id").as_llong()},
			{"idParent", -1},
			{"dataType", "JunctionGroup"},
			{"data", ss.str()}
		};
		j.merge_patch(jsonDB);
		pool->Add(std::make_shared<FWorldJunctionGroup>(j));
	}

	// Projection.
	std::string projStr;
	glm::dvec4 falseOrigin(0.0);

	// Add headers.
	pool->Header = std::make_shared<FWorldEntityHeader>();
	pugi::xml_node header = top.child("header");
	if (header)
	{
		pugi::xml_node geo = header.child("geoReference");
		if (geo)
		{
			pugi::xml_node projstr = geo.first_child();
			if (projstr)
				projStr = projstr.text().as_string();

			std::string projZoneIfUtm;
			{
				size_t zoneIdx = projStr.find("+zone=");
				if (zoneIdx != std::string::npos)
				{
					size_t zoneBegin = zoneIdx + 6;
					size_t zoneEnd = zoneBegin;
					for (; zoneEnd < projStr.size(); zoneEnd++)
					{
						if (!std::isdigit(projStr[zoneEnd])) break;
					}
					if (zoneBegin < zoneEnd)
					{
						projZoneIfUtm = projStr.substr(zoneBegin, zoneEnd - zoneBegin);
					}
				}
			}

			pugi::xml_attribute falseX = geo.attribute("originLong");
			if (falseX)
				falseOrigin.x = falseX.as_double(0.0);

			pugi::xml_attribute falseY = geo.attribute("originLat");
			if (falseY)
				falseOrigin.y = falseY.as_double(0.0);

			pugi::xml_attribute falseZ = geo.attribute("originAlt");
			if (falseZ)
				falseOrigin.z = falseZ.as_double(0.0);

			pugi::xml_attribute falseR = geo.attribute("originHdg");
			if (falseR)
				falseOrigin.w = falseR.as_double(0.0);

			if (!projZoneIfUtm.empty())
			{
				std::stringstream ss;
				ss << std::fixed << std::setprecision(17) << falseOrigin.x;

				std::string utmX = ss.str();
				if (utmX.compare(0, projZoneIfUtm.length(), projZoneIfUtm) == 0)
				{
					utmX.erase(0, projZoneIfUtm.length());
					falseOrigin.x = std::stod(utmX);
				}
			}
		}
	}
	pool->Header->GeoReference = projStr;
	pool->Header->Axis = FWorldEntityAxis::FromOpenDRIVE(projStr);
	pool->Header->Axis.FalseOrigin = falseOrigin;

	return pool;
}

void FWorldEntityManagerDriverFile::SetRegion(
	const FWorldEntityManagerPool::Ptr& pool,
	const std::unordered_set<FWorldEntity::Ptr>& newEntities,
	const std::unordered_set<FWorldEntity::Ptr>& dirtyEntities,
	const std::unordered_set<FWorldEntity::Ptr>& removedEntities,
	int world, int rev)
{
	(void)pool; (void)newEntities; (void)dirtyEntities; (void)removedEntities; (void)world; (void)rev;
}
